#ifndef _IMPLEMENTATION_HPP
#define _IMPLEMENTATION_HPP

#include "interface.hpp"
#include "data.hpp"
#include "stopwatch.hpp"

#include <CL/cl.hpp>
#include <limits>
#include <string>
#include <iostream>


static inline void checkErr(cl_int err, const char * name){
    if (err != CL_SUCCESS) {
	    std::cerr << "ERROR: " << name << " (" << err << ")" << std::endl;
	    exit(EXIT_FAILURE);
    }
}

struct CL_uint4{
	unsigned x, y, z, w;
	CL_uint4(unsigned X,unsigned Y, unsigned Z, unsigned W) : x(X), y(Y), z(Z),w(W){}
	CL_uint4(): x(0),y(0),z(0),w(0){}
};


//typedef cl_uint4 CL_uint4;

static std::pair<unsigned,unsigned> copy(const std::vector<unsigned> & set1, const std::vector<unsigned> & set2, std::vector<CL_uint4> & data){
	data.reserve(set1.size() / 4 + set2.size()/4);
	unsigned m = std::numeric_limits<unsigned>::max();
	unsigned M = std::numeric_limits<unsigned>::min();
	for(size_t t = 0, T = set1.size(); t < T; t+=4){
		data.push_back(CL_uint4(set1[t],set1[t+1],set1[t+2],set1[t+3]));
		for(int i = 0; i < 4; ++i){
			if(set1[t + i] < m )
				m = set1[i];
			if (set1[t +i] > M )
				M = set1[i];	
		}
	}
	for(size_t t = 0, T = set2.size(); t < T; t+=4){
		data.push_back(CL_uint4(set2[t],set2[t+1],set2[t+2],set2[t+3]));
		for(int i = 0; i < 4; ++i){
                        if(set2[t + i] < m )
                                m = set2[i];
                        if (set2[t+i] > M )
                                M = set2[i];
                }
	}
	return std::make_pair(m,M);
}


/*
 * Final implementation of the tested OCL program.
 */
class OCLProgram_Implementation : public OCLProgram
{
public:
	OCLProgram_Implementation(const DataIn &dataIn, DataOut &dataOut) : OCLProgram(dataIn, dataOut)
	{
		// This is the best place to initialize mKernels member field.
		// i.e. mKernels["kernelName"] = Source("kernelFile.cl", true);
		mKernels["hello"] = Source("hello.cl",true);
		mKernels["sortElements"] = Source("sortElements.cl",true);
		mKernels["minp"] = Source("minp.cl",true);
	}

	virtual int run(const cl::Context &context, const std::vector<cl::Device> &devices,
		std::map<std::string, cl::Kernel> &kernels, bool verbose)
	{
		/*
		 * Implementation of the main algorithm.
		 * Note that you have mDataIn and mDataOut member fields at your disposal.
		 */
		cl_int err;

		std::string hw= "Hello World from GPU\n";
		
		char * outH = new char[hw.length()+1];
		cl::Buffer outCL(  context, CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR, hw.length()+1, outH, &err);
	    	checkErr(err, "Buffer::Buffer()");
		
		cl::Kernel & kernel = kernels["hello"];
		err = kernel.setArg( 0, outCL );
		checkErr( err, "Kernel::setArg(0,outCl)" );		
		cl::CommandQueue queue(context, devices[0], 0, &err);
		checkErr(err, "CommandQueue::CommandQueue()");


		cl::Event event;
		err = queue.enqueueNDRangeKernel(kernel,cl::NullRange,cl::NDRange(hw.length()+1),cl::NDRange(1, 1),NULL,&event);
		checkErr(err, "ComamndQueue::enqueueNDRangeKernel()");
		
		event.wait();
		err = queue.enqueueReadBuffer(outCL,CL_TRUE,0,hw.length()+1,outH);
		checkErr(err, "ComamndQueue::enqueueReadBuffer()");
		std::cout << outH;
		delete outH;

		
		Stopwatch * stopwatch = NULL;

                if ( verbose ){
                        stopwatch = new Stopwatch(true);
                }
		std::cerr << "unsigned == clint " <<(sizeof(unsigned) == sizeof(cl_int)) << "\n";
		
		std::vector<CL_uint4> data;
		std::pair<unsigned,unsigned> mm = copy(mDataIn.set1,mDataIn.set2,data);
		unsigned min = mm.first;
		unsigned max = mm.second;

		// Prepare data buffers ...
// 		cl::Buffer buf[] = { 
//															//not actually modifying values	
//				cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, sizeof(cl_int)*mDataIn.set1.size(), (void*)&mDataIn.set1.at(0)),
//				cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, sizeof(cl_int)*mDataIn.set2.size(), (void*)&mDataIn.set2.at(0)),
//				cl::Buffer(context, CL_MEM_WRITE_ONLY, sizeof(cl_int)*mDataIn.set1.size()),
//		};
		cl::Buffer buffer = cl::Buffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, sizeof(CL_uint4)*data.size(),&data.at(0));
		
		if (verbose) {
			stopwatch->stop();
			std::cout << "Creating buffers: " << stopwatch->getMiliseconds() << " ms" << std::endl;
			stopwatch->start();
		}

		kernel = kernels["sortElements"];
		err = kernel.setArg( 0, buffer );
		checkErr( err, "Kernel::setArg(0,buffer)" );

		err = queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(data.size()), cl::NDRange(1,1), NULL, NULL);
		checkErr( err, "commandQueue::enqueueNDRangeKernel2()" );
		
		queue.finish();		

		err = queue.enqueueReadBuffer(buffer, CL_TRUE, 0,sizeof(CL_uint4)*data.size(),&data.at(0));
		checkErr( err, "commandQueue::enequeueReadBuffer()" );
		if (verbose){
			stopwatch->stop();
			bool ok = true;
	                for(size_t i = 0; i < data.size(); ++i ){
        	                ok = ok && ( data[i].x <= data[i].y && data[i].y <= data[i].z && data[i].z <= data[i].w);
	                } 
			std::cerr << (ok ? "sortElements ok" : "sortElements fail") <<" " << stopwatch->getMiliseconds() << "ms" << std::endl;
			stopwatch->start();
		}




		return EXIT_SUCCESS;	// Success
	}
};


#endif
